package com.hys.app.service.erp.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.poi.excel.ExcelUtil;
import cn.hutool.poi.excel.ExcelWriter;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.hys.app.converter.erp.ProcurementContractProductConverter;
import com.hys.app.converter.erp.WarehouseEntryBatchConverter;
import com.hys.app.converter.erp.WarehouseEntryConverter;
import com.hys.app.converter.erp.WarehouseEntryProductConverter;
import com.hys.app.framework.context.user.AdminUserContext;
import com.hys.app.framework.database.WebPage;
import com.hys.app.framework.exception.ServiceException;
import com.hys.app.framework.database.mybatisplus.base.BaseServiceImpl;
import com.hys.app.framework.rabbitmq.MessageSender;
import com.hys.app.framework.rabbitmq.MqMessage;
import com.hys.app.framework.util.CurrencyUtil;
import com.hys.app.framework.util.JsonUtil;
import com.hys.app.framework.util.PageConvert;
import com.hys.app.mapper.erp.WarehouseEntryMapper;
import com.hys.app.model.base.SettingGroup;
import com.hys.app.model.base.rabbitmq.AmqpExchange;
import com.hys.app.model.erp.dos.ProcurementContract;
import com.hys.app.model.erp.dos.ProcurementContractProduct;
import com.hys.app.model.erp.dos.ProcurementPlan;
import com.hys.app.model.erp.dos.*;
import com.hys.app.model.erp.dto.WarehouseEntryDTO;
import com.hys.app.model.erp.dto.WarehouseEntryProductDTO;
import com.hys.app.model.erp.dto.WarehouseEntryQueryParams;
import com.hys.app.model.erp.dto.WarehouseEntryStatisticsParam;
import com.hys.app.model.erp.dto.message.WarehouseEntryAuditPassMessage;
import com.hys.app.model.erp.enums.*;
import com.hys.app.model.erp.vo.WarehouseEntryAllowable;
import com.hys.app.model.erp.vo.WarehouseEntryStatistics;
import com.hys.app.model.erp.vo.WarehouseEntryVO;
import com.hys.app.model.goods.dos.CategoryDO;
import com.hys.app.model.system.dos.AdminUser;
import com.hys.app.model.system.dos.DeptDO;
import com.hys.app.model.system.vo.SiteSetting;
import com.hys.app.service.base.service.SettingManager;
import com.hys.app.service.erp.*;
import com.hys.app.service.goods.CategoryManager;
import com.hys.app.service.system.AdminUserManager;
import com.hys.app.service.system.DeptManager;
import org.assertj.core.util.Lists;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

import static com.hys.app.framework.util.CollectionUtils.convertMap;

/**
 * 入库单业务层实现
 *
 * @author 张崧
 * @since 2023-12-05 11:25:07
 */
@Service
public class WarehouseEntryManagerImpl extends BaseServiceImpl<WarehouseEntryMapper, WarehouseEntryDO> implements WarehouseEntryManager {

    @Autowired
    private WarehouseEntryConverter converter;

    @Autowired
    private WarehouseEntryProductConverter itemConverter;

    @Autowired
    private WarehouseEntryBatchConverter batchConverter;

    @Autowired
    private WarehouseEntryProductManager warehouseEntryProductManager;

    @Autowired
    private WarehouseEntryBatchManager warehouseEntryBatchManager;

    @Autowired
    private NoGenerateManager noGenerateManager;

    @Autowired
    private ProcurementContractProductManager contractProductManager;

    @Autowired
    private DeptManager deptManager;

    @Autowired
    private SupplierManager supplierManager;

    @Autowired
    private WarehouseManager warehouseManager;

    @Autowired
    private ProcurementContractManager contractManager;

    @Autowired
    private ProcurementPlanManager procurementPlanManager;

    @Autowired
    private WarehouseEntryMapper warehouseEntryMapper;

    @Autowired
    private MessageSender messageSender;

    @Autowired
    private AdminUserManager adminUserManager;

    @Autowired
    private SettingManager settingManager;

    @Autowired
    private ProductManager productManager;

    @Autowired
    private ProcurementContractProductConverter procurementContractProductConverter;

    @Autowired
    private CategoryManager categoryManager;

    @Autowired
    private FinanceItemManager financeItemManager;

    @Override
    public WebPage<WarehouseEntryVO> list(WarehouseEntryQueryParams queryParams) {
        WebPage<WarehouseEntryDO> webPage = baseMapper.selectPage(queryParams);

        return converter.convert(webPage);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void add(WarehouseEntryDTO warehouseEntryDTO) {
        check(warehouseEntryDTO);

        // 保存入库单
        WarehouseEntryDO warehouseEntryDO = converter.combination(warehouseEntryDTO);
        warehouseEntryDO.setSn(noGenerateManager.generate(NoBusinessTypeEnum.WarehouseEntry, warehouseEntryDTO.getDeptId()));
        warehouseEntryDO.setStatus(WarehouseEntryStatusEnum.NotSubmit);
        warehouseEntryDO.setSupplierSettlementFlag(false);
        save(warehouseEntryDO);

        // 保存入库单商品明细
        List<WarehouseEntryProductDO> productList = itemConverter.combination(warehouseEntryDTO.getProductList(),
                warehouseEntryDO.getId(), warehouseEntryDTO.getContractProductMap(), getPriceRatio());
        warehouseEntryProductManager.saveBatch(productList);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void edit(WarehouseEntryDTO warehouseEntryDTO) {
        // 更新前校验
        WarehouseEntryDO oldDO = getById(warehouseEntryDTO.getId());
        if (!new WarehouseEntryAllowable(oldDO).getEdit()) {
            throw new ServiceException("当前状态不允许编辑");
        }

        check(warehouseEntryDTO);

        // 更新入库单
        WarehouseEntryDO warehouseEntryDO = converter.combination(warehouseEntryDTO);
        updateById(warehouseEntryDO);

        // 更新入库单商品明细
        List<WarehouseEntryProductDO> productList = itemConverter
                .combination(warehouseEntryDTO.getProductList(), warehouseEntryDO.getId(), warehouseEntryDTO.getContractProductMap(), getPriceRatio());
        warehouseEntryProductManager.deleteByWarehouseEntryIds(Collections.singletonList(warehouseEntryDTO.getId()));
        warehouseEntryProductManager.saveBatch(productList);
    }

    @Override
    public WarehouseEntryVO getDetail(Long id) {
        WarehouseEntryDO warehouseEntryDO = getById(id);
        List<WarehouseEntryProductDO> productList = warehouseEntryProductManager.listByWarehouseEntryId(id);
        List<ProcurementContractProduct> contractProductList = contractProductManager.list(warehouseEntryDO.getContractId());

        List<WarehouseEntryBatchDO> batchList = warehouseEntryBatchManager.listByWarehouseEntryIds(Collections.singletonList(id));

        return converter.combination(warehouseEntryDO, itemConverter.convert(productList), contractProductList, batchConverter.convert(batchList));
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void delete(List<Long> ids) {
        List<WarehouseEntryDO> warehouseEntryList = listByIds(ids);
        for (WarehouseEntryDO warehouseEntryDO : warehouseEntryList) {
            if (!new WarehouseEntryAllowable(warehouseEntryDO).getDelete()) {
                throw new ServiceException(warehouseEntryDO.getSn() + "不能进行删除操作");
            }
        }
        removeBatchByIds(ids);
        warehouseEntryProductManager.deleteByWarehouseEntryIds(ids);
    }

    @Override
    public void submit(List<Long> ids) {
        // 校验
        List<WarehouseEntryDO> list = listByIds(ids);
        for (WarehouseEntryDO warehouseEntryDO : list) {
            if (!new WarehouseEntryAllowable(warehouseEntryDO).getSubmit()) {
                throw new ServiceException(warehouseEntryDO.getSn() + "当前状态不允许提交");
            }
        }

        lambdaUpdate()
                .set(WarehouseEntryDO::getStatus, WarehouseEntryStatusEnum.Submit)
                .in(WarehouseEntryDO::getId, ids)
                .update();
    }

    @Override
    public void withdraw(Long id) {
        // 校验
        WarehouseEntryDO oldDO = getById(id);
        if (!new WarehouseEntryAllowable(oldDO).getWithdraw()) {
            throw new ServiceException("当前状态不允许撤销");
        }

        lambdaUpdate()
                .set(WarehouseEntryDO::getStatus, WarehouseEntryStatusEnum.NotSubmit)
                .eq(WarehouseEntryDO::getId, id)
                .update();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void audit(List<Long> ids, WarehouseEntryStatusEnum status, String remark) {
        if (status != WarehouseEntryStatusEnum.AuditPass && status != WarehouseEntryStatusEnum.AuditReject) {
            throw new ServiceException("审核参数错误");
        }

        List<WarehouseEntryDO> warehouseEntryList = listByIds(ids);

        for (WarehouseEntryDO warehouseEntryDO : warehouseEntryList) {
            // 校验
            if (!new WarehouseEntryAllowable(warehouseEntryDO).getAudit()) {
                throw new ServiceException(warehouseEntryDO.getSn() + "不能进行审核操作");
            }
        }

        lambdaUpdate()
                // 如果是驳回,状态改为未提交状态
                .set(WarehouseEntryDO::getStatus, status == WarehouseEntryStatusEnum.AuditReject ? WarehouseEntryStatusEnum.NotSubmit : status)
                .set(WarehouseEntryDO::getAuditRemark, remark)
                .set(WarehouseEntryDO::getAuditBy, AdminUserContext.getAdminUserName())
                .in(WarehouseEntryDO::getId, ids)
                .update();

        // 如果审核通过
        if (status == WarehouseEntryStatusEnum.AuditPass) {
            for (WarehouseEntryDO warehouseEntryDO : warehouseEntryList) {
                List<WarehouseEntryProductDO> productList = warehouseEntryProductManager.listByWarehouseEntryId(warehouseEntryDO.getId());
                // 更新合同明细的已入库数量
                contractProductManager.updateStockNum(warehouseEntryDO.getContractId(), productList);

                // 创建入库批次
                List<WarehouseEntryBatchDO> batchList = converter.combination(warehouseEntryDO, productList, noGenerateManager);
                warehouseEntryBatchManager.create(StockChangeSourceEnum.WAREHOUSE_ENTRY, warehouseEntryDO.getSn(), batchList);

                // 生成财务明细
                double totalPrice = CurrencyUtil.sum(productList, WarehouseEntryProductDO::getTotalPrice);
                financeItemManager.addExpand(FinanceExpandTypeEnum.Purchase, warehouseEntryDO.getSn(), totalPrice);
            }

            // 发送入库单审核通过消息
            WarehouseEntryAuditPassMessage message = new WarehouseEntryAuditPassMessage();
            message.setList(warehouseEntryList);
            this.messageSender.send(new MqMessage(AmqpExchange.WAREHOUSE_ENTRY_AUDIT_PASS, AmqpExchange.WAREHOUSE_ENTRY_AUDIT_PASS + "_ROUTING", message));
        }

    }

    @Override
    public void settlementComplete(Long supplierSettlementId, List<Long> ids) {
        lambdaUpdate()
                .set(WarehouseEntryDO::getSupplierSettlementFlag, true)
                .set(WarehouseEntryDO::getSupplierSettlementId, supplierSettlementId)
                .in(WarehouseEntryDO::getId, ids)
                .update();
    }

    @Override
    public void settlementCancel(Long supplierSettlementId) {
        lambdaUpdate()
                .set(WarehouseEntryDO::getSupplierSettlementFlag, false)
                .set(WarehouseEntryDO::getSupplierSettlementId, null)
                .eq(WarehouseEntryDO::getSupplierSettlementId, supplierSettlementId)
                .update();
    }

    /**
     * 查询入库单统计分页列表数据
     *
     * @param params 查询参数
     * @return
     */
    @Override
    public WebPage statistics(WarehouseEntryStatisticsParam params) {
        IPage<WarehouseEntryStatistics> iPage = this.warehouseEntryMapper.selectWarehouseEntryPage(new Page(params.getPageNo(), params.getPageSize()), params);
        return PageConvert.convert(iPage);
    }

    /**
     * 导出入库单统计列表数据
     *
     * @param response
     * @param params   查询参数
     */
    @Override
    public void export(HttpServletResponse response, WarehouseEntryStatisticsParam params) {
        //查询出库单商品列表
        List<WarehouseEntryStatistics> list = this.warehouseEntryMapper.selectWarehouseEntryList(params);

        ArrayList<Map<String, Object>> rows = CollUtil.newArrayList();
        ExcelWriter writer = ExcelUtil.getWriter(true);
        for (WarehouseEntryStatistics statistics : list) {
            Map<String, Object> map = new LinkedHashMap<>();
            map.put("所属部门", statistics.getDeptName());
            map.put("仓库名称", statistics.getWarehouseName());
            map.put("供应商", statistics.getSupplierName());
            map.put("入库单编号", statistics.getEntrySn());
            map.put("商品编号", statistics.getSn());
            map.put("商品名称", statistics.getName());
            map.put("商品类别", statistics.getCategoryName());
            map.put("规格型号", statistics.getSpecification());
            map.put("单位", statistics.getUnit());
            map.put("入库数量", statistics.getNum());
            rows.add(map);
        }

        writer.write(rows, true);

        ServletOutputStream out = null;
        try {
            out = response.getOutputStream();
        } catch (IOException e) {
            e.printStackTrace();
        }
        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8");
        response.setHeader("Content-Disposition", "attachment;filename=warehouse_entry.xlsx");
        writer.flush(out, true);
        writer.close();
        IoUtil.close(out);
    }

    /**
     * 判断合同是否已被入库单占用
     *
     * @param contractIds 合同ID集合
     * @return
     */
    @Override
    public boolean checkContractIsUsed(List<Long> contractIds) {
        long count = this.lambdaQuery().in(WarehouseEntryDO::getContractId, contractIds).count();
        if (count > 0) {
            return true;
        }
        return false;
    }

    /**
     * 判断采购计划是否已被入库单占用
     *
     * @param planIds 采购计划ID集合
     * @return
     */
    @Override
    public boolean checkPlanIsUsed(List<Long> planIds) {
        long count = this.lambdaQuery().in(WarehouseEntryDO::getPurchasePlanId, planIds).count();
        if (count > 0) {
            return true;
        }
        return false;
    }

    @Override
    public long countByWarehouseId(Long warehouseId) {
        return lambdaQuery().eq(WarehouseEntryDO::getWarehouseId, warehouseId).count();
    }

    private void check(WarehouseEntryDTO warehouseEntryDTO) {

        if (warehouseEntryDTO.getPurchasePlanId()!=null){
            ProcurementPlan procurementPlan = procurementPlanManager.getById(warehouseEntryDTO.getPurchasePlanId());
            if (procurementPlan == null) {
                throw new ServiceException("采购计划不存在");
            }
            warehouseEntryDTO.setProcurementPlan(procurementPlan);
        }

        DeptDO deptDO = deptManager.getDept(warehouseEntryDTO.getDeptId());
        if (deptDO == null) {
            throw new ServiceException("部门不存在");
        }

        WarehouseDO warehouseDO = warehouseManager.getById(warehouseEntryDTO.getWarehouseId());
        if (warehouseDO == null) {
            throw new ServiceException("仓库不存在");
        }

        SupplierDO supplierDO = supplierManager.getById(warehouseEntryDTO.getSupplierId());
        if (supplierDO == null) {
            throw new ServiceException("供应商不存在");
        }

        AdminUser handledBy = adminUserManager.getModel(warehouseEntryDTO.getHandledById());
        if (handledBy == null) {
            throw new ServiceException("经手人不存在");
        }

        Map<Long, WarehouseEntryProductDTO> warehouseEntryProductDTOMap = convertMap(warehouseEntryDTO.getProductList(), WarehouseEntryProductDTO::getProductId, Function.identity());

        if (warehouseEntryDTO.getContractId()!=null){
            ProcurementContract contract = contractManager.getById(warehouseEntryDTO.getContractId());
            if (contract == null) {
                throw new ServiceException("合同不存在");
            }
            if (!ContractStatusEnum.EXECUTING.name().equals(contract.getContractStatus())) {
                throw new ServiceException("只能选择执行中的合同");
            }
            // 查询合同商品明细
            List<ProcurementContractProduct> contractProductList = contractProductManager.list(warehouseEntryDTO.getContractId());
            Map<Long, ProcurementContractProduct> contractProductMap = convertMap(contractProductList, ProcurementContractProduct::getProductId, Function.identity());
            List<Long> noContractProductIds = Lists.newArrayList();
            // 校验入库的商品
            for (WarehouseEntryProductDTO warehouseEntryProductDTO : warehouseEntryDTO.getProductList()) {
                ProcurementContractProduct contractProduct = contractProductMap.get(warehouseEntryProductDTO.getProductId());
                if (contractProduct != null) {
                    // 本次最大可入库数量 = 合同数量 - 已入库数量
                    int maxEntryNum = contractProduct.getNum() - contractProduct.getStockNum();

                    if (warehouseEntryProductDTO.getNum() > maxEntryNum) {
                        throw new ServiceException("商品:" + contractProduct.getProductName() + "的入库数量不能超过最大可入库数量:" + maxEntryNum);
                    }
                }else{
                    noContractProductIds.add(warehouseEntryProductDTO.getProductId());
                }
            }

            if (CollectionUtil.isNotEmpty(noContractProductIds)){
                contractProductMap = convertNoContractProduct(contractProductMap,noContractProductIds,warehouseEntryProductDTOMap);
            }

            warehouseEntryDTO.setContract(contract);
            warehouseEntryDTO.setContractProductMap(contractProductMap);
        }else{
            List<Long> noContractProductIds = warehouseEntryDTO.getProductList().stream().map(WarehouseEntryProductDTO::getProductId).collect(Collectors.toList());
            Map<Long, ProcurementContractProduct> contractProductMap = convertNoContractProduct(new HashMap(),noContractProductIds,warehouseEntryProductDTOMap);
            warehouseEntryDTO.setContractProductMap(contractProductMap);
        }

        warehouseEntryDTO.setDeptDO(deptDO);
        warehouseEntryDTO.setSupplierDO(supplierDO);
        warehouseEntryDTO.setWarehouseDO(warehouseDO);
        warehouseEntryDTO.setHandledBy(handledBy);
    }

    /**
     * 组装不在合同范围内商品数据
     * @param contractProductMap 组装后的商品数据
     * @param noContractProductIds 不在合同内的商品id集合
     * @param warehouseEntryProductMap
     */
    private Map<Long, ProcurementContractProduct> convertNoContractProduct(Map<Long, ProcurementContractProduct> contractProductMap, List<Long> noContractProductIds, Map<Long, WarehouseEntryProductDTO> warehouseEntryProductMap) {
        //查询不在合同内的商品基本信息
        QueryWrapper<ProductDO> productWrapper = new QueryWrapper();
        productWrapper.in("id", noContractProductIds);
        List<ProductDO> noContractProducts = productManager.list(productWrapper);
        Map<Long, ProductDO> noContractProductMap = convertMap(noContractProducts, ProductDO::getId, Function.identity());
        //查询分类信息
        List<Long> categoryIds = noContractProducts.stream().map(ProductDO::getCategoryId).collect(Collectors.toList());
        List<CategoryDO> categories = categoryManager.list(new QueryWrapper<CategoryDO>().in("category_id", categoryIds));
        Map<Long, CategoryDO> categoryMap = convertMap(categories, CategoryDO::getCategoryId, Function.identity());
        //组装数据
        noContractProductIds.forEach(id -> {
            ProductDO productDO = noContractProductMap.get(id);
            ProcurementContractProduct procurementContractProduct = procurementContractProductConverter.convert(productDO, categoryMap, warehouseEntryProductMap);
            contractProductMap.put(id,procurementContractProduct);
        });
        return contractProductMap;
    }

    private Double getPriceRatio() {
        String s = settingManager.get(SettingGroup.SITE);
        SiteSetting siteSetting = JsonUtil.jsonToObject(s, SiteSetting.class);
        Double priceRatio = siteSetting.getPriceRatio();
        if (priceRatio == null) {
            throw new ServiceException("进货价系数未设置，请先设置");
        }
        return priceRatio;
    }

}

